package jp.dodododo.dao.util;

import static jp.dodododo.dao.types.SQLTypes.*;

import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.CharBuffer;
import java.sql.SQLXML;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import jp.dodododo.dao.types.JavaType;
import jp.dodododo.dao.types.JavaTypes;
import jp.dodododo.dao.types.SQLType;
import jp.dodododo.dao.types.SQLTypes;

/**
 * 
 * @author Satoshi Kimura
 */
public abstract class TypesUtil {
	@SuppressWarnings("unchecked")
	private static final Map<Class, SQLType> SQL_TYPES = new HashMap<Class, SQLType>();

	@SuppressWarnings("unchecked")
	private static final Map<Class, JavaType> JAVA_TYPES = new HashMap<Class, JavaType>();

	static {
		addType(Object.class, SQLTypes.OBJECT, JavaTypes.OBJECT);
	}

	static {
		addType(CharSequence.class, STRING, JavaTypes.STRING);
		addType(String.class, STRING, JavaTypes.STRING);
		addType(StringBuilder.class, STRING, JavaTypes.STRING_BUILDER);
		addType(StringBuffer.class, STRING, JavaTypes.STRING_BUFFER);
		addType(CharBuffer.class, STRING, JavaTypes.CHAR_BUFFER);
	}

	static {
		addType(byte.class, NUMBER, JavaTypes.BYTE);
		addType(Byte.class, NUMBER, JavaTypes.BYTE);
		addType(short.class, NUMBER, JavaTypes.SHORT);
		addType(Short.class, NUMBER, JavaTypes.SHORT);
		addType(int.class, NUMBER, JavaTypes.INTEGER);
		addType(Integer.class, NUMBER, JavaTypes.INTEGER);
		addType(long.class, NUMBER, JavaTypes.LONG);
		addType(Long.class, NUMBER, JavaTypes.LONG);
		addType(float.class, NUMBER, JavaTypes.FLOAT);
		addType(Float.class, NUMBER, JavaTypes.FLOAT);
		addType(double.class, NUMBER, JavaTypes.DOUBLE);
		addType(Double.class, NUMBER, JavaTypes.DOUBLE);
	}

	static {
		addType(BigDecimal.class, NUMBER, JavaTypes.BIG_DECIMAL);
		addType(BigInteger.class, NUMBER, JavaTypes.BIG_INTEGER);
	}

	static {
		addType(AtomicInteger.class, NUMBER, JavaTypes.ATOMIC_INTEGER);
		addType(AtomicLong.class, NUMBER, JavaTypes.ATOMIC_LONG);
	}

	static {
		addType(boolean.class, BOOLEAN, JavaTypes.BOOLEAN);
		addType(Boolean.class, BOOLEAN, JavaTypes.BOOLEAN);
	}

	static {
		addType(java.sql.Date.class, DATE, JavaTypes.SQL_DATE);
		addType(java.sql.Time.class, TIME, JavaTypes.TIME);

		addType(Timestamp.class, TIMESTAMP, JavaTypes.TIMESTAMP);
		addType(java.util.Date.class, TIMESTAMP, JavaTypes.TIMESTAMP);
		addType(Calendar.class, TIMESTAMP, JavaTypes.CALENDAR);
	}

	static {
		addType(byte[].class, BINARY, JavaTypes.BYTE_ARRAY);
		addType(InputStream.class, BINARY, JavaTypes.INPUT_STREAM);
	}

	static {
		addType(SQLXML.class, SQLXML, JavaTypes.SQLXML);
	}

	static {
		// addType(null, NULL, JavaTypes.NULL);
	}

	public static void addType(Class<?> clazz, SQLType sqlType, JavaType<?> javaType) {
		SQL_TYPES.put(clazz, sqlType);
		JAVA_TYPES.put(clazz, javaType);
	}

	public static SQLType getSQLType(int type) {
		switch (type) {
		case Types.TINYINT:
		case Types.SMALLINT:
		case Types.INTEGER:
		case Types.BIGINT:
		case Types.REAL:
		case Types.FLOAT:
		case Types.DOUBLE:
		case Types.DECIMAL:
		case Types.NUMERIC:
			return NUMBER;
		case Types.CHAR:
		case Types.LONGVARCHAR:
		case Types.VARCHAR:
		case Types.NVARCHAR:
		case Types.LONGNVARCHAR:
		case Types.NCHAR:
			return STRING;
		case Types.DATE:
			return DATE;
		case Types.TIME:
			return TIME;
		case Types.TIMESTAMP:
			return TIMESTAMP;
		case Types.BOOLEAN:
			return BOOLEAN;
		case Types.BINARY:
		case Types.VARBINARY:
		case Types.LONGVARBINARY:
		case Types.BLOB:
		case Types.CLOB:
		case Types.NCLOB:
			return BINARY;
		case Types.JAVA_OBJECT:
		case Types.SQLXML:
			return SQLXML;
		case Types.OTHER:
			return OBJECT;
			// FIXME
			// case Types.BIT:
			// case Types.DATALINK:
			// case Types.DISTINCT:
			// case Types.REF:
			// case Types.ROWID:
			// case Types.STRUCT:
			// case Types.ARRAY:
		default:
			return OBJECT;
		}
	}

	public static SQLType getSQLType(Object value) {
		if (value == null) {
			return NULL;
		}
		return getSQLType(value.getClass());
	}

	public static SQLType getSQLType(Class<?> clazz) {
		SQLType type = SQL_TYPES.get(clazz);
		if (type != null) {
			return type;
		}
		Class<?>[] interfaces = clazz.getInterfaces();
		if (interfaces != null) {
			for (Class<?> interfaceClass : interfaces) {
				type = getSQLType(interfaceClass);
				if (type != null) {
					return type;
				}
			}
		}
		Class<?> superClass = clazz.getSuperclass();
		if (superClass == null) {
			return OBJECT;
		} else {
			return getSQLType(superClass);
		}
	}

	public static JavaType<?> getJavaType(Class<?> clazz) {
		JavaType<?> type = JAVA_TYPES.get(clazz);
		if (type != null) {
			return type;
		}
		Class<?>[] interfaces = clazz.getInterfaces();
		if (interfaces != null) {
			for (Class<?> interfaceClass : interfaces) {
				type = getJavaType(interfaceClass);
				if (type != null) {
					return type;
				}
			}
		}
		Class<?> superClass = clazz.getSuperclass();
		if (superClass == null) {
			return JavaTypes.OBJECT;
		} else {
			return getJavaType(superClass);
		}
	}

}
